/*
 * Copyright (C) 2015 grandcentrix GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.grandcentrix.tray.provider;

import net.grandcentrix.tray.core.TrayLog;

import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteQueryBuilder;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.DataAbilityHelper;
import ohos.aafwk.ability.PathMatcher;
import ohos.aafwk.content.Intent;
import ohos.app.Context;
import ohos.bundle.AbilityInfo;
import ohos.data.dataability.DataAbilityPredicates;
import ohos.data.rdb.RdbUtils;
import ohos.data.resultset.ResultSet;
import ohos.data.resultset.CombinedResultSet;
import ohos.data.rdb.ValuesBucket;
import ohos.utils.net.Uri;
import org.jetbrains.annotations.NonNls;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class TrayContentProvider extends Ability {
    private static final int SINGLE_PREFERENCE = 10;

    private static final int MODULE_PREFERENCE = 20;

    private static final int ALL_PREFERENCE = 30;

    private static final int INTERNAL_SINGLE_PREFERENCE = 110;

    private static final int INTERNAL_MODULE_PREFERENCE = 120;

    private static final int INTERNAL_ALL_PREFERENCE = 130;

    private static PathMatcher sURIMatcher;

    String pass ="pass";

    TrayDBHelper mDeviceDbHelper;

    TrayDBHelper mUserDbHelper;



    @Override
    public int delete(final Uri uri, DataAbilityPredicates dataAbilityPredicates) {

        final int match = sURIMatcher.getPathId(uri.getDecodedPath());
        String selection = dataAbilityPredicates.getWhereClause();
        String[] selectionArgs = new String[dataAbilityPredicates.getWhereArgs().size()];
        dataAbilityPredicates.getWhereArgs().toArray(selectionArgs);

        switch (match) {
            case SINGLE_PREFERENCE:
            case INTERNAL_SINGLE_PREFERENCE:
                selection = SqliteHelper.extendSelection(selection,
                        TrayContract.Preferences.Columns.KEY + " = ?");
                selectionArgs = SqliteHelper.extendSelectionArgs(selectionArgs,
                        new String[]{uri.getDecodedPathList().get(2)});
                // no break
            case MODULE_PREFERENCE:
            case INTERNAL_MODULE_PREFERENCE:
                selection = SqliteHelper.extendSelection(selection,
                        TrayContract.Preferences.Columns.MODULE + " = ?");
                selectionArgs = SqliteHelper.extendSelectionArgs(selectionArgs,
                        new String[]{uri.getDecodedPathList().get(1)});
                // no break
            case ALL_PREFERENCE:
            case INTERNAL_ALL_PREFERENCE:
                break;
            default:
                throw new IllegalArgumentException("Delete is not supported for Uri: " + uri);
        }

        final int rows;
        final String backup = uri.getFirstQueryParamByKey("backup");
        if (backup == null) {
            int device = mDeviceDbHelper.getWritableDatabase(pass)
                    .delete(getTable(uri), selection, selectionArgs);
            int user = mUserDbHelper.getWritableDatabase(pass)
                    .delete(getTable(uri), selection, selectionArgs);
            rows = device + user;
        } else {
            rows = getWritableDatabase(uri)
                    .delete(getTable(uri), selection, selectionArgs);
        }

        // Don't force an UI refresh if nothing has changed
        if (rows > 0) {
            DataAbilityHelper.creator(getApplicationContext()).notifyChange(uri);
        }

        return rows;
    }

    public SQLiteDatabase getReadableDatabase(final Uri uri) {
        if (shouldBackup(uri)) {
            return mUserDbHelper.getReadableDatabase(pass);
        } else {
            return mDeviceDbHelper.getReadableDatabase(pass);
        }
    }


        /**
         * get location of the data
         *
         * @param uri localtion of the data
         * @return correct sqlite table for the given uri
         */
    public String getTable(Uri uri) {
        if (uri == null) {
            return null;
        }
        final int match = sURIMatcher.getPathId(String.valueOf(uri));
        switch (match) {
            case SINGLE_PREFERENCE:
            case MODULE_PREFERENCE:
            case ALL_PREFERENCE:
            default:
                return TrayDBHelper.TABLE_NAME;

            case INTERNAL_SINGLE_PREFERENCE:
            case INTERNAL_MODULE_PREFERENCE:
            case INTERNAL_ALL_PREFERENCE:
                return TrayDBHelper.INTERNAL_TABLE_NAME;
        }
    }

    @Override
    public String getType( Uri uri) {
        return null;
    }

    public SQLiteDatabase getWritableDatabase(final Uri uri) {
        if (shouldBackup(uri)) {
            return mUserDbHelper.getWritableDatabase(pass);
        } else {
            return mDeviceDbHelper.getWritableDatabase(pass);
        }
    }

    @Override
    public int insert(final Uri uri, final ValuesBucket values) {
        Date date = new Date();
        final int match = sURIMatcher.getPathId(String.valueOf(uri));
        switch (match) {
            case SINGLE_PREFERENCE:
            case INTERNAL_SINGLE_PREFERENCE:
                // Add created and updated dates
                values.putString(TrayContract.Preferences.Columns.CREATED, String.valueOf(date.getTime()));
                values.putString(TrayContract.Preferences.Columns.UPDATED, String.valueOf(date.getTime()));
                values.putString(TrayContract.Preferences.Columns.MODULE, uri.getDecodedPathList().get(1));
                values.putString(TrayContract.Preferences.Columns.KEY, uri.getDecodedPathList().get(2));
                break;

            default:
                throw new IllegalArgumentException("Insert is not supported for Uri: " + uri);
        }

        final String prefSelection =
                TrayContract.Preferences.Columns.MODULE + " = ?"
                        + "AND " + TrayContract.Preferences.Columns.KEY + " = ?";
        final String[] prefSelectionArgs = {
                values.getString(TrayContract.Preferences.Columns.MODULE),
                values.getString(TrayContract.Preferences.Columns.KEY)
        };

        final String[] excludeForUpdate = {TrayContract.Preferences.Columns.CREATED};

       final int status = insertOrUpdate(getWritableDatabase(uri), getTable(uri),
                prefSelection, prefSelectionArgs, values, excludeForUpdate);

       if (status >= 0) {
            DataAbilityHelper.creator(getContext()).notifyChange(uri);
            return status;

        } else if (status == -1) {
           //throw new SQLiteException("An error occurred while saving preference.");
           TrayLog.w("Couldn't update or insert data. Uri: " + uri);
        } else {
            TrayLog.w("unknown SQLite error");
        }
        return -1;
    }

    public int insertOrUpdate(final SQLiteDatabase writableDatabase, final String table,
                              final String prefSelection, final String[] prefSelectionArgs,
                              final ValuesBucket values, final String[] excludeForUpdate) {
        return SqliteHelper
                .insertOrUpdate(writableDatabase, table, prefSelection, prefSelectionArgs, values,
                        excludeForUpdate);
    }

    @Override
    public void onStart(Intent intent) {
        mUserDbHelper = new TrayDBHelper(getContext(), true);
        mDeviceDbHelper = new TrayDBHelper(getContext(), false);
    }

    public void attachInfo(Context context, AbilityInfo info) {
        setAuthority(info.label);
        TrayLog.v("TrayContentProvider registered for authority: " + info.label);
    }

    @Override
    public ResultSet query(final Uri uri, final String[] projection, final DataAbilityPredicates dataAbilityPredicates) {
        final SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
        final int match = sURIMatcher.getPathId(uri.getDecodedPath());
        String selection = dataAbilityPredicates.getWhereClause();
        String[] selectionArgs = new String[dataAbilityPredicates.getWhereArgs().size()];
        dataAbilityPredicates.getWhereArgs().toArray(selectionArgs);

        switch (match) {
            case SINGLE_PREFERENCE:
            case INTERNAL_SINGLE_PREFERENCE:
                builder.appendWhere(
                        TrayContract.Preferences.Columns.KEY + " = " +
                                RdbUtils.escapeQuote(uri.getDecodedPathList().get(2)));
                // no break
            case MODULE_PREFERENCE:
            case INTERNAL_MODULE_PREFERENCE:
                if (match == SINGLE_PREFERENCE
                        || match == INTERNAL_SINGLE_PREFERENCE) {
                    builder.appendWhere(" AND ");
                }
                builder.appendWhere(
                        TrayContract.Preferences.Columns.MODULE + " = " +
                                RdbUtils.escapeQuote(uri.getDecodedPathList().get(1)));
                // no break
            case ALL_PREFERENCE:
            case INTERNAL_ALL_PREFERENCE:
                builder.setTables(getTable(uri));
                break;
            default:
                throw new IllegalArgumentException("Query is not supported for Uri: " + uri);
        }

        final ResultSet cursor;
        final String backup = uri.getFirstQueryParamByKey("backup");
        if (backup == null) {
            // backup not set, query both dbs
            ResultSet cursor1 = builder
                    .query(getReadableDatabase(uri), projection, selection,
                            selectionArgs, null, null, null);
            ResultSet cursor2 = builder
                    .query(mDeviceDbHelper.getReadableDatabase(pass), projection, selection,
                            selectionArgs, null, null,null);

            cursor = new CombinedResultSet(new ResultSet[]{cursor1, cursor2});
        } else {
            // Query
            cursor = builder.query(getReadableDatabase(uri), projection, selection,
                    selectionArgs, null, null, null);
        }

        List<Uri> uris = new ArrayList<Uri>();
        uris.add(uri);
        if (cursor != null) {
            cursor.setAffectedByUris(DataAbilityHelper.creator(getApplicationContext()), uris);
        }
        return cursor;
    }

    @Override
    public void onStop() {
        mUserDbHelper.close();
        mDeviceDbHelper.close();
    }

    @Override
    public int update(final Uri uri, final ValuesBucket values, DataAbilityPredicates dataAbilityPredicates) {
        throw new UnsupportedOperationException("not implemented");

        // this is a standard implementation (but untested at the moment).
        // Perhaps useful in the future. The current implementation doesn't require a an
        // update mechanism other than multiple calls to {@link #insert} which results
        // in an {@link #insertOrUpdate} call

        /*final int match = sURIMatcher.match(uri);
        switch (match) {
            case SINGLE_PREFERENCE:
            case INTERNAL_SINGLE_PREFERENCE:
                // Add updated date
                values.put(TrayContract.Preferences.Columns.UPDATED, new Date().getTime());
                break;
            default:
                throw new IllegalArgumentException("Update is not supported for Uri: " + uri);
        }

        final int rows = mUserDbHelper.getWritableDatabase()
                .update(getTable(uri), values, selection, selectionArgs);

        // Don't force an UI refresh if nothing has changed
        if (rows > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }

        return rows;*/
    }

    boolean shouldBackup(@NonNls final Uri uri) {
        final String backup = uri.getFirstQueryParamByKey("backup");
        return !"false".equals(backup);
    }

    static void setAuthority(final String authority) {
        sURIMatcher = new PathMatcher();

        sURIMatcher.addPath(TrayContract.Preferences.BASE_PATH,
                ALL_PREFERENCE);

        // BASE/module
        sURIMatcher.addPath(
                TrayContract.Preferences.BASE_PATH + "/*",
                MODULE_PREFERENCE);

        // BASE/module/key
        sURIMatcher.addPath(
                TrayContract.Preferences.BASE_PATH + "/*/*",
                SINGLE_PREFERENCE);

        sURIMatcher.addPath(
                TrayContract.InternalPreferences.BASE_PATH,
                INTERNAL_ALL_PREFERENCE);

        // INTERNAL_BASE/module
        sURIMatcher.addPath(
                TrayContract.InternalPreferences.BASE_PATH + "/*",
                INTERNAL_MODULE_PREFERENCE);

        // INTERNAL_BASE/module/key
        sURIMatcher.addPath(
                TrayContract.InternalPreferences.BASE_PATH + "/*/*",
                INTERNAL_SINGLE_PREFERENCE);
    }

}
